package com.lanzhou.yuanfen.security.session;


import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.web.authentication.session.SessionAuthenticationException;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Comparator;
import java.util.List;

/**
 * 源码分析: 模仿当前配置生效的: ConcurrentSessionControlAuthenticationStrategy
 * 在登入后直接会调用当前注入的策略的onAuthentication()方法
 * 自定义Session认证策略(登入的时候会从过滤器中获取对应的策略, 将当前用户认证信息传入)
 *
 * @author Administrator
 */
@Component
public class EnhanceSessionAuthenticationStrategy implements MessageSourceAware, SessionAuthenticationStrategy {

    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

    @Resource
    private EnhanceSessionRegistryImpl enhanceSessionRegistryImpl;

    /**
     * 用户数量是否超过最大
     */
    private boolean exceptionIfMaximumExceeded = false;

    /**
     * 初始值最大为1
     */
    private int maximumSessions = 1;

    /**
     * 自定义登入端点的key走这个方法去将session存储在全局会话中
     * 放弃手动添加, 让监听器去做
     * SessionListener.attributeAdded
     *
     * @param authentication
     * @param request
     * @param response
     * @throws SessionAuthenticationException
     */
    @Override
    public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) throws
            SessionAuthenticationException {
        // 调用自定义增强Session策略注册一个新的Session(模仿默认的 RegisterSessionAuthenticationStrategy)
        enhanceSessionRegistryImpl.registerNewSession(request.getSession().getId(), authentication.getPrincipal());
        // 将其添加到全局Session中, 放弃手动添加, 让监听器去做
        // globalSessionContext.add(request.getSession());

        final List<SessionInformation> sessions = enhanceSessionRegistryImpl.getAllSessions(
                authentication.getPrincipal(), false);
        int sessionCount = sessions.size();
        int allowedSessions = getMaximumSessionsForThisUser(authentication);
        if (sessionCount < allowedSessions) {
            // They haven't got too many login sessions running at present
            return;
        }
        if (allowedSessions == -1) {
            // We permit unlimited logins
            return;
        }
        if (sessionCount == allowedSessions) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                // Only permit it though if this request is associated with one of the
                // already registered sessions
                for (SessionInformation si : sessions) {
                    if (si.getSessionId().equals(session.getId())) {
                        return;
                    }
                }
            }
            // If the session is null, a new one will be created by the parent class,
            // exceeding the allowed number
        }
        allowableSessionsExceeded(sessions, allowedSessions, enhanceSessionRegistryImpl);
    }


    /**
     * 允许操作会话数量做的逻辑
     *
     * @param sessions
     * @param allowableSessions
     * @param registry
     * @throws SessionAuthenticationException
     */
    private void allowableSessionsExceeded(List<SessionInformation> sessions,
                                           int allowableSessions, SessionRegistry registry)
            throws SessionAuthenticationException {
        if (exceptionIfMaximumExceeded || (sessions == null)) {
            throw new SessionAuthenticationException(messages.getMessage(
                    "ConcurrentSessionControlAuthenticationStrategy.exceededAllowed",
                    new Object[]{Integer.valueOf(allowableSessions)},
                    "Maximum sessions of {0} for this principal exceeded"));
        }

        // Determine least recently used sessions, and mark them for invalidation
        sessions.sort(Comparator.comparing(SessionInformation::getLastRequest));
        int maximumSessionsExceededBy = sessions.size() - allowableSessions + 1;
        List<SessionInformation> sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy);
        for (SessionInformation session : sessionsToBeExpired) {
            session.expireNow();
        }
    }

    private int getMaximumSessionsForThisUser(Authentication authentication) {
        return maximumSessions;
    }


    /**
     * Sets the {@link MessageSource} used for reporting errors back to the user when the
     * user has exceeded the maximum number of authentications.
     */
    @Override
    public void setMessageSource(MessageSource messageSource) {
        Assert.notNull(messageSource, "messageSource cannot be null");
        this.messages = new MessageSourceAccessor(messageSource);
    }
}
